/*
 * Proto Streamer
 * Copyright (C) 2009 John Pritchard.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 */
package p.c.http;

import p.c.Protocol;
import p.c.Version;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import static java.net.Proxy.Type.*;
import java.net.ProxySelector;
import java.net.Socket;
import java.net.URL;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.List;

/**
 * Streaming HTTP client.  
 * 
 * @author jdp
 */
public final class Connection 
    extends java.net.HttpURLConnection
{
    public static class PComp 
        extends Object
        implements java.util.Comparator<Proxy>
    {
        public final static java.util.Comparator<Proxy> Instance = new PComp();

        PComp(){
            super();
        }

        public int compare(Proxy a, Proxy b){
            Proxy.Type at = a.type();
            if (at == b.type())
                return 0;
            else {
                switch (at){
                case DIRECT:
                case HTTP:
                    return 1;
                default:
                    return -1;
                }
            }
        }
        public boolean equals(Object that){
            return (this == that);
        }
    }

    protected final Proxy[] proxies;
    protected final boolean mayConnectDirect, mayConnectProxy;

    protected final Protocol protocol = Protocol.HTTP10;
    private int soto = 0;
    private Socket socket;
    private Proxy selected;
    private InputStream in;
    private OutputStream out;
    private Request request;
    private Response response;


    public Connection(URL url){
        super(url);
        Proxy[] ary = null;
        try {
            ProxySelector router = ProxySelector.getDefault();
            List<Proxy> list = router.select(url.toURI());
            if (null != list &&(!list.isEmpty())){
                ary = list.toArray(new Proxy[list.size()]);
                Arrays.sort(ary,0,ary.length,PComp.Instance);
            }
            else
                ary = new Proxy[]{Proxy.NO_PROXY};
        }
        catch (Exception exc){
            System.out.println(Version.Name+" proxy selection unavailable ");

            ary = new Proxy[]{Proxy.NO_PROXY};
        }
        this.proxies = ary;
        boolean mayConnectDirect = false;
        boolean mayConnectProxy = false;
        for (int cc = 0, count = ary.length; cc < count; cc++){
            Proxy p = ary[cc];
            switch (p.type()){
            case DIRECT:
                mayConnectDirect = true;
                break;
            case SOCKS:
            case HTTP:
                mayConnectProxy = true;
                break;
            }
        }
        this.mayConnectDirect = mayConnectDirect;
        this.mayConnectProxy = mayConnectProxy;
    }


    public boolean isOpen(){
        return (null != this.socket);
    }
    public boolean isNotOpen(){
        return (null == this.socket);
    }
    public Proxy[] getProxies(){
        return this.proxies;
    }
    public boolean hasProxySelected(){
        return (null != this.selected);
    }
    public Proxy getProxySelected(){
        return this.selected;
    }
    public boolean usingProxy(){
        Proxy selected = this.selected;
        if (null != selected)
            return (DIRECT != selected.type());
        else
            return (!this.mayConnectDirect);
    }
    public boolean mayConnectDirect(){
        return this.mayConnectDirect;
    }
    public boolean mayConnectProxy(){
        return this.mayConnectProxy;
    }
    public int getSocketTimeout(){
        return this.soto;
    }
    public void setSocketTimeout(int soto){
        if (-1 < soto)
            this.soto = soto;
        else
            throw new IllegalArgumentException(String.valueOf(soto));
    }
    public void disconnect(){
        this.in = null;
        this.out = null;
        this.request = null;
        this.response = null;
        Socket socket = this.socket;
        if (null != socket){
            this.socket = null;
            try {
                socket.close();
            }
            catch (java.io.IOException exc){
                exc.printStackTrace();
            }
        }
    }
    public void connect() 
        throws IOException 
    {
        this.getOutputStream();
    }
    protected Socket open() 
        throws IOException 
    {
        Socket socket = this.socket;
        if (null == socket){
            if (this.mayConnectDirect){
                String host = this.url.getHost();
                int port = this.url.getPort();
                if (1 > port)
                    port = 80;

                socket = new Socket(host,port);
                socket.setSoTimeout(this.soto);

                this.socket = socket;
                this.selected = Proxy.NO_PROXY;

                return socket;
            }
            else if (this.mayConnectProxy){
                Proxy[] proxies = this.proxies;
                for (int cc = 0, count = proxies.length; cc < count; cc++){
                    Proxy p = proxies[cc];
                    switch (p.type()){
                    case SOCKS:{
                        socket = new Socket(p);
                        socket.setSoTimeout(this.soto);

                        this.socket = socket;
                        this.selected = p;

                        System.out.println(Version.Name+" selected proxy "+p);

                        return socket;
                    }
                    case HTTP:{
                        InetSocketAddress hostport = (InetSocketAddress)p.address();
                        InetAddress proxyHost = hostport.getAddress();
                        int proxyPort = hostport.getPort();
                        if (0 > proxyPort)
                            proxyPort = 80;

                        socket = new Socket(proxyHost,proxyPort);
                        socket.setSoTimeout(this.soto);

                        this.socket = socket;
                        this.selected = p;

                        System.out.println(Version.Name+" selected proxy "+p);

                        return socket;
                    }
                    }
                }
                throw new java.net.NoRouteToHostException("Applicable proxy not found from available.");
            }
            else
                throw new java.net.NoRouteToHostException("Proxy not found");
        }
        else
            return socket;
    }
    protected InputStream openInputStream() 
        throws IOException 
    {
        InputStream in = this.in;
        if (null == in){
            Socket socket = this.open();
            in = socket.getInputStream();
            this.in = in;
        }
        return in;
    }
    protected OutputStream openOutputStream() 
        throws IOException 
    {
        OutputStream out = this.out;
        if (null == out){
            Socket socket = this.open();
            out = socket.getOutputStream();
            this.out = out;
        }
        return out;
    }
    public Request getRequest() 
        throws IOException 
    {
        Request request = this.request;
        if (null == request){

            request = new Request(this);
            this.request = request;
        }
        return request;
    }
    public Response getResponse() 
        throws IOException 
    {
        Response response = this.response;
        if (null == response){
            response = new Response(this);
            this.response = response;
        }
        return response;
    }
    public InputStream getInputStream() 
        throws IOException 
    {
        return this.getResponse().getInputStream();
    }
    public OutputStream getOutputStream() 
        throws IOException 
    {
        return this.getRequest().getOutputStream();
    }
    public OutputStream getOutputStreamer() 
        throws IOException 
    {
        Response response = this.getResponse();
        response.getInputStream();
        if (response.isOk())
            return this.getRequest().getOutputStream();
        else
            throw new IOException(response.getResponseline());
    }
    protected java.lang.String getRequestLine(){
        java.lang.String path = this.url.getPath();
        java.lang.String qs = this.url.getQuery();
        if (null != qs){
            path += ('?'+qs);
        }
        java.lang.String rf = this.url.getRef();
        if (null != rf){
            path += ('#'+rf);
        }
        if (this.usingProxy()){
            java.lang.String hostport = this.getHostPort();
            
            java.lang.String requestline = (this.method+" http://"+hostport+path+' '+this.protocol+"\r\n");

            return requestline;
        }
        else {
            java.lang.String requestline = (this.method+' '+path+' '+this.protocol+"\r\n");
            return requestline;
        }
    }
    public void setRequestProperty(String name, String value){
        try {
            this.getRequest().setHeader(name,value);
        }
        catch (IOException exc){
            throw new IllegalStateException(exc);
        }
    }
    public String getProtocol(){
        return this.url.getProtocol();
    }
    public String getHost(){
        return this.url.getHost();
    }
    public int getPort(){
        return this.url.getPort();
    }
    public int getDefaultPort(){
        return this.url.getDefaultPort();
    }
    public String getHostPort(){
        String host = this.getHost();
        if (null != host){
            int port = this.getPort();
            if (-1 == port)
                return host;
            else {
                int dport = this.getDefaultPort();
                if (port == dport)
                    return host;
                else 
                    return (host+':'+port);
            }
        }
        else
            return null;
    }
    public String getAuthority(){
        return this.url.getAuthority();
    }
    public String getUserInfo(){
        return this.url.getUserInfo();
    }
    public String getPath(){
        return this.url.getPath();
    }
    public String getFile(){
        return this.url.getFile();
    }
    public String getQuery(){
        return this.url.getQuery();
    }
    public String getRef(){
        return this.url.getRef();
    }
}
